﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Linq.Expressions;

namespace Goldlight.Mvvm.Base
{
    /// <summary>
    /// An editable object allows the user to start making changes, and then end the changes if they need to.
    /// Any ViewModel that needs to implement an editable object should inherit from this class rather than from
    /// <see cref="ViewModelBase"/>.
    /// </summary>
    public abstract class EditableViewModelBase : WorkspaceViewModelBase
    {
        #region Members
        private EditableViewModelBase _copy;
        private bool _inEdit = false;
        private List<string> _changedFields;
        #endregion

        #region Public methods
        /// <summary>
        /// Initializes a new instance of <see cref="EditableViewModelBase"/>.
        /// </summary>
        public EditableViewModelBase() : base() { }

        /// <summary>
        /// When the edit starts, the current state of the object is stored in a copy. This copy is
        /// used to restore the object to the original state if <see cref="CancelEdit"/> is called.
        /// </summary>
        public void BeginEdit()
        {
            _inEdit = true;
            FieldInfo[] fis = this.GetType().GetFields(System.Reflection.BindingFlags.Instance |
                BindingFlags.Public | BindingFlags.FlattenHierarchy |
                BindingFlags.NonPublic);
            object item = Activator.CreateInstance(this.GetType(), false);
            foreach (FieldInfo fi in fis)
            {
                fi.SetValue(item, fi.GetValue(this));
            }
            _copy = item as EditableViewModelBase;
        }

        /// <summary>
        /// Called when the edit has been cancelled, this method restores the properties back to the state they
        /// were in before the edit began.
        /// </summary>
        public void CancelEdit()
        {
            if (_inEdit)
            {
                FieldInfo[] fis = this.GetType().GetFields(System.Reflection.BindingFlags.Instance |
                    BindingFlags.Public | BindingFlags.FlattenHierarchy |
                    BindingFlags.NonPublic);

                foreach (FieldInfo fi in fis)
                {
                    fi.SetValue(this, fi.GetValue(_copy));
                }
                _copy = null;
                _changedFields = null;
                _inEdit = false;
                OnChanged(string.Empty);
            }
        }

        /// <summary>
        /// Called when the edit has successfully completed, this method is responsible for "committing" the 
        /// model.
        /// </summary>
        public void EndEdit()
        {
            if (_inEdit)
            {
                _copy = null;
                PlayChanged();
                _changedFields = null;
                _inEdit = false;
                OnChanged(string.Empty);
            }
        }

        #endregion


        #region Protected properties and methods
        /// <summary>
        /// Called when a property has changed.
        /// </summary>
        /// <param name="propertyName">The name of the property that has changed.</param>
        /// <remarks><para>If the property is changed when the application is in "edit" mode, the actual
        /// change notifications are stored in a list which are sent when <see cref="EndEdit"/> is called.</para>
        /// <para>If the property is chaned and the application is not in "edit" mode, the property changed
        /// event is raised immediately.</para></remarks>
        protected void PlayChanged(string propertyName)
        {
            if (_inEdit)
            {
                if (_changedFields == null)
                    _changedFields = new List<string>();
                _changedFields.Add(propertyName);
            }
            else
            {
                OnChanged(propertyName);
            }
        }
        #endregion

        #region Private methods
        /// <summary>
        /// This method "plays" the changes so that the bindings get updated when the object has changed.
        /// </summary>
        private void PlayChanged()
        {
            if (_changedFields == null || _changedFields.Count == 0)
                return;
            foreach (string propertyName in _changedFields)
            {
                OnChanged(propertyName);
            }
        }
        #endregion

    }
}